AWS Lambda Powertools TypeScript がbeta releaseされたので触ってみた。
AWS Lambda Powertools TypeScript がbetaリリースされたと古巣のCDOが教えてくれたので触ってみました?
AWSの中の人のツイートはこれです。
I am ✨thrilled✨ to announce that the beta release of AWS Lambda Powertools TypeScript is public?
It provides a suite of utilities for Node.js Lambda functions to ease the adoption of best practices such as tracing, structured logging, custom metrics ✨https://t.co/6xYpOEY5zM
— Sara Gerion (@Sarutule) January 5, 2022
is何?
端的に言うと、Lambdaを実装する上でのUtilityライブラリをまとめたものです。
AWS Lambda Powertools はもともと、 pythonと javaがAWS Labsから出てて、Node versionはDAZNがOSSとして公開してくれていました。今回はAWS LabsからTypeScript製のものがbetaリリースされた形です。
リポジトリ: https://github.com/awslabs/aws-lambda-powertools-typescript/
ドキュメント: https://awslabs.github.io/aws-lambda-powertools-typescript/latest/
注意
ドキュメントにも大きく掲載されているとおり、現在はあくまでbeta developer previewであり、
Do not use this library for production workloads.
と明記されています。プロダクト投入についてはGAを待ちましょう!
使い方
ライブラリは以下の4つに分かれています。使いたいものを選んでinstallするのが良さそうです。
npm install @aws-lambda-powertools/commons npm install @aws-lambda-powertools/tracer npm install @aws-lambda-powertools/logger npm install @aws-lambda-powertools/metrics
Exampleをデプロイしてみる。
リポジトリにはCDKのexampleも内包されていたので、早速デプロイしてみようと思います。(npm workspace使ってるー。いいねー)
Deployする前にExampleの中身を読んで見る。
やってることとしては
- ExampleのLambdaをそれぞれデプロイする
- それらを2回ずつ実行する
という内容になっていました!
デプロイされるLambdaのコードの中に実装サンプルとその説明が書いてあったので、とりあえずデプロイしてみて実装とLogやMetricなどの結果を合わせて見ていこうと思います。
今度こそデプロイしてみる
じゃあREADMEにあるとおりnpm i
してー。。。というところで、lockファイルが変更されてしまいました。READMEには書いてないけど余計な差分くらわないためにnpm ci
のほうが良さそうですね。ということで、トップレベルとexample/cdk
でそれぞれnpm ci
します。
できたらおもむろにcdk deploy
。
npm ci cd examples/cdk npm ci npm run cdk deploy
どうなった?
CDKの中でデプロイされたLambdaを実行する仕掛けも入っているので、CDKをデプロイするだけでLogとかMetricsの結果を確認できるようになっています。 Exampleのコード辺と一緒にその結果を見ていこうと思います。
Logger
以下のようなコードを書くと、
import { Context } from 'aws-lambda'; import { Logger } from '@aws-lambda-powertools/logger'; const serviceName = 'MyFunctionWithStandardHandler'; const logger = new Logger({ logLevel: 'INFO', serviceName: serviceName }); export const handler = async (event: unknown, context: Context): Promise<void> => { // ### Experiment with Logger logger.addContext(context); logger.addPersistentLogAttributes({ testKey: 'testValue', }); logger.info('This is an INFO log'); ...
↓こんなログが吐かれます。
2022-02-15T03:31:38.712Z b7ce5cf3-e9e1-435d-aede-78b7f8cc3dec INFO { "cold_start": true, "function_arn": "arn:aws:lambda:ap-northeast-1:123456789012:function:LambdaPowertoolsTypeScript-Exam-MyFunctionXXXXXXXX-XXXXXXXXXX", "function_memory_size": 128, "function_name": "LambdaPowertoolsTypeScript-Exam-MyFunctionXXXXXXXX-XXXXXXXXXX", "function_request_id": "b7ce5cf3-e9e1-435d-aede-78b7f8cc3dec", "level": "INFO", "message": "This is an INFO log", "service": "MyFunctionWithStandardHandler", "timestamp": "2022-02-15T03:31:38.683Z", "testKey": "testValue" }
構造化ログを吐いてくれるのはDAZNのPowerToolsと同じですね。嬉しい。
addPersistentLogAttributes
で、各ログに毎回埋め込まれる値をセットできるのも使い所がありそう。
Metrics
以下のようなコードを書くと、
import { Context } from 'aws-lambda'; import { Metrics, MetricUnits } from '@aws-lambda-powertools/metrics'; const namespace = 'CDKExample'; const serviceName = 'MyFunctionWithStandardHandler'; const metrics = new Metrics({ namespace: namespace, serviceName: serviceName }); export const handler = async (event: unknown, context: Context): Promise<void> => { // ### Experiment with Metrics metrics.captureColdStartMetric(); metrics.throwOnEmptyMetrics(); metrics.setDefaultDimensions({ environment: 'example', type: 'standardFunction' }); metrics.addMetric('test-metric', MetricUnits.Count, 10); const metricWithItsOwnDimensions = metrics.singleMetric(); metricWithItsOwnDimensions.addDimension('InnerDimension', 'true'); metricWithItsOwnDimensions.addMetric('single-metric', MetricUnits.Percent, 50); metrics.publishStoredMetrics(); metrics.throwOnEmptyMetrics(); ...
以下のメトリクスがputされます。(ちょっとわかりづらそうですが。)
↓ColdStartの発生率とか分析できそう。
↓デフォルトのDimensionsをセットしておける。
↓設定を引き継いだ個別のインスタンスを生成できる。
総じて、Dimensionsをコンテキストとして扱えるのが嬉しいですね。
加えて、内部実装的にはSDKなどでputMetricData
をしておらず、console.log()
をしているだけになっています。これはAmazon CloudWatch Embedded Metric Format (EMF)という仕組みで、CloudWatch Logsに出力された内容を非同期的にメトリクス化してくれる仕組みです。
これによりLambdaのランタイムでは標準出力に吐くだけなので、AWS SDKでputMetricData
を実施する場合に比べてパフォーマンスへの影響を少なくできます。さらに、内部実装的にはメトリクスで出力する内容を溜めておいてpublishStoredMetrics()
を呼ばれたときにまとめて標準出力に吐くので、よりパフォーマンス面に考慮されているといえます。
一方で、コスト的にはカスタムメトリクスとCloudWatch Logsの両方のコストが計上されるので、大量にメトリクスを送信する場合には注意が必要かもしれません。
Tracer
import { Context } from 'aws-lambda'; import { Tracer } from '@aws-lambda-powertools/tracer'; const serviceName = 'MyFunctionWithStandardHandler'; const tracer = new Tracer({ serviceName: serviceName }); export const handler = async (event: unknown, context: Context): Promise<void> => { // Since we are in manual mode we need to create the handler segment (the 4 lines below would be done for you by decorator/middleware) // we do it at the beginning because we want to trace the whole duration of the handler const segment = tracer.getSegment(); // This is the facade segment (the one that is created by AWS Lambda) // Create subsegment for the function & set it as active const handlerSegment = segment.addNewSubsegment(`## ${process.env._HANDLER}`); tracer.setSegment(handlerSegment); // Annotate the subsegment with the cold start & serviceName tracer.annotateColdStart(); tracer.addServiceNameAnnotation(); // ... // なんかしら、エレガントな処理がここに入る // ... // ### Experiment with Tracer // This annotation & metadata will be added to the handlerSegment subsegment (## index.handler) tracer.putAnnotation('awsRequestId', context.awsRequestId); tracer.putMetadata('eventPayload', event); // Create another subsegment & set it as active const subsegment = handlerSegment.addNewSubsegment('### MySubSegment'); tracer.setSegment(subsegment); let res; try { res = { foo: 'bar' }; tracer.addResponseAsMetadata(res, process.env._HANDLER); } catch (err) { // Add the error as metadata subsegment.addError(err as Error, false); throw err; } finally { // Close subsegments (the AWS Lambda one is closed automatically) subsegment.close(); // (### MySubSegment) handlerSegment.close(); // (## index.handler) // Set the facade segment as active again (the one created by AWS Lambda) tracer.setSegment(segment); } };
というような実装をすると、以下のようにX-Rayを確認できます。
セグメントごとの処理時間を確認したり、
セグメントごとにアノテーションやメタデータを埋めておくとそれらも確認することができます。
アノテーションとメタデータの使い分けとしては、アノテーションはTracesの検索に使うことができ、メタデータはネストしたJSONオブジェクトをそのまま登録できます。
アノテーションであるColdStart
を用いて検索する場合はannotation.ColdStart = true
のように検索窓に入力します。
まとめ
いずれの機能もLambdaで記述されたアプリケーションをCloudWatchをより簡単に可視化するための便利な機能が備わっているという印象です。 冒頭にも述べたとおり、まだbeta developer previewです。betaの解除を待ちましょう!